﻿using System;
using System.Collections.Generic;
using System.Text;
using System.Net;
using System.IO; 
using Lidgren.Network;
using IMLibrary; 
using IMLibrary.BinaryPacket;  
using IMLibrary.IO;
using IMLibrary.Net.Enum;
using IMLibrary.Server;
using IMLibrary.WS;
using IMLibrary.WS.Net;
using IMLibrary.WS.Server;

namespace Ourmsg.Server
{
    /// <summary>
    /// 图片文件传输服务器
    /// </summary>
    public class ImageFileServer : WebSocketService
    {
        /// <summary>
        /// 图片文件传输服务器
        /// </summary>
        /// <param name="Port">服务端口</param>
        public ImageFileServer(int tcpPort, int udpPort)
        {
            this.TcpPort = tcpPort;
            this.UdpPort = udpPort;

            #region websocket
            Path = "/Images";
            WSServer = new WebSocketServer(TcpPort);
            WSServer.KeepClean = true;
            WSServer.AddWebSocketService<WebSocketSession>(Path, () => new WebSocketSession(this));
            WSServer.AuthenticationSchemes = IMLibrary.WS.Net.AuthenticationSchemes.Basic;
            WSServer.Realm = "OurMsg Server";
            WSServer.UserCredentialsFinder = ID =>
            {
                var name = ID.Name;
                // Return user name, password, and roles.If the user credentials aren't found,null.
                return name == "Ourmsg" ? new IMLibrary.WS.Net.NetworkCredential(name, "123456@Ourmsg.net") : null;
            };
            #endregion
        }

        #region 字段 
        /// <summary>
        /// UDP服务
        /// </summary>
        private UDPServer udpServer = null; 
        /// <summary>
        /// 图片文件中转服务
        /// </summary>
        private Dictionary<string, ServerFileInfo> Images = null;
        /// <summary>
        /// 图片文件中转服务时钟
        /// </summary>
        private System.Timers.Timer TimerImageServer = null;
        #endregion

        #region 属性
        /// <summary>
        /// 最大在线用户数
        /// </summary>
        private uint OnlineUserMax
        { set; get; }
        /// <summary>
        /// 面向公网的 TCP 服务端口
        /// </summary>
        private int TcpPort
        { set; get; }
        /// <summary>
        /// 面向公网的 UDP 服务端口
        /// </summary>
        private int UdpPort
        { set; get; }


        #endregion

        #region 公共方法
        /// <summary>
        /// 开始服务
        /// </summary>
        public void Start()
        {
            ServerPower(true);

            WSServer.Start();
        }

        /// <summary>
        /// 停止服务
        /// </summary>
        public void Stop()
        {
            ServerPower(false);
        }

        #region 服务开关
        private void ServerPower(bool IsOpen)
        {
            if (IsOpen)
            {
                #region 创建图片文件夹
                System.IO.DirectoryInfo dInfo = new System.IO.DirectoryInfo("Images");
                if (!dInfo.Exists)
                    dInfo.Create();
                #endregion

                if (Images == null)
                    Images = new Dictionary<string, ServerFileInfo>();

                if (TimerImageServer == null)
                {
                    TimerImageServer = new System.Timers.Timer();
                    TimerImageServer.Elapsed += new System.Timers.ElapsedEventHandler(TimerImageServer_Elapsed);
                }

                TimerImageServer.Interval = 60000;//设置图片文件缓存时间 （默认1分钟）
                TimerImageServer.Enabled = true;//开始服务
            }
            else
            {
                if (TimerImageServer != null)
                    TimerImageServer.Enabled = false;//停止文件服务检测

                if (Images != null)
                {
                    Images.Clear();
                    Images = null;
                }
            }

            #region UDP消息服务器
            if (IsOpen)
            {
                if (udpServer == null)
                {
                    udpServer = new UDPServer();
                    udpServer.Connected += (s, e) => e.Session.IsAuthenticated = true;
                    udpServer.RecivedMsg += udpServer_RecivedMsg;
                    udpServer.Disconnected += udpServer_Disconnected;
                    udpServer.Start(UdpPort);
                }
            }
            else
            {
                if (udpServer != null)
                {
                    udpServer.RecivedMsg -= udpServer_RecivedMsg;
                    udpServer.Disconnected -= udpServer_Disconnected;
                    udpServer.Stop();
                    udpServer = null;
                }
            }
            #endregion

            if (IsOpen)
            {
                WSServer.Start();
            }
            else
            {
                WSServer.Stop();
            }
        }
        #endregion

        #endregion

        public override void OnOpen(WebSocketSession session)
        {
             
        }

        public override void OnMessage(WebSocketSession session, MessageEventArgs e)
        {
            if (e.RawData.Length < BinaryFileMsg.HeaderLength)  //收到非法消息
            { Sessions.CloseSession(session.ID); return; }

            if (DateTime.Now > session.StartTime.AddSeconds(60))
            {//如果客户端连接服务器的时间大于60秒，则视为非法攻击，断开联接
                Sessions.CloseSession(session.ID); return;
            } 
            PacketReceived(e.RawData, session, null);
        }

        public override void OnClose(WebSocketSession session, CloseEventArgs e)
        {
             
        }

        public override void OnError(WebSocketSession session, IMLibrary.WS.ErrorEventArgs e)
        {
           
        }
        private void udpServer_Disconnected(object sender, UDPEventArgs e)
        {

        }

        private void udpServer_RecivedMsg(object sender, UDPEventArgs e)
        {
            if (e.Data.Length < BinaryFileMsg.HeaderLength)  //收到非法消息 
            { DisconnectUDP(e.Session); return; }
             
            e.Session.IsAuthenticated = true;

            if (DateTime.Now > e.Session.ConnectTime.AddSeconds(60))
            {//如果客户端连接服务器的时间大于60秒，则视为非法攻击，断开联接
                DisconnectUDP(e.Session); return;
            }
            PacketReceived(e.Data,null, e.Session, false);
        }

        #region 从内存字典中获取服务缓冲区图片文件
        /// <summary>
        /// 从内存字典中获取服务缓冲区图片文件
        /// </summary>
        /// <param name="MD5">图片MD5值</param>
        /// <returns></returns>
        private ServerFileInfo getServerImage(string MD5)
        {
            ServerFileInfo image = null;
            if (Images.ContainsKey(MD5)) //如果文件存在
                Images.TryGetValue(MD5, out image);
            return image;
        }
        #endregion

        #region 图片文件服务

        #region 图片缓冲服务周期
        private void TimerImageServer_Elapsed(object sender, System.Timers.ElapsedEventArgs e)
        {
            string fileFullName = "";

            ServerFileInfo[] dfiles = new ServerFileInfo[Images.Count];
            Images.Values.CopyTo(dfiles, 0);
            foreach (var serverImage in dfiles)
                if (DateTime.Now > serverImage.LastActivity.AddHours(1))
                {
                    //如果文件有一小时未用，则删除内存信息并删除磁盘文件
                    fileFullName = "Images\\" + serverImage.MD5 + serverImage.Extension;
                    System.IO.File.Delete(fileFullName);

                    serverImage.Data = null;
                    Images.Remove(serverImage.MD5);//删除并释放内存
                }
                else if (DateTime.Now > serverImage.LastActivity.AddMinutes(1))
                {
                    //如果文件10分钟未用，则清除内存中的文件内容，释放内存
                    serverImage.Data = null;
                    Images.Remove(serverImage.MD5);//删除并释放内存
                }
                else if (DateTime.Now > serverImage.LastActivity.AddMinutes(2))//如果2分钟后文件未上传完成，则视为破图
                {
                    if (serverImage.Data.Length != serverImage.Length)
                    {
                        serverImage.Data = null;
                        Images.Remove(serverImage.MD5);//删除并释放内存
                    }
                }
        }
        #endregion

        private void PacketReceived(byte[] data, WebSocketSession session, NetConnection UdpSession, bool isTcp = true)
        {
            BinaryFileMsg FileMsg = new BinaryFileMsg(data);
            var image = getServerImage(FileMsg.MD5);
            string fullFileName = "Images\\" + FileMsg.MD5 + FileMsg.Extension;//标记文件路径

            #region 在内存中为文件创建空间
            if (FileMsg.CommandType == (byte)FileServerCommandType.New)//上传文件请求
            {
                if (FileMsg.Length > 1024000 * 2 || FileMsg.Length <= 0)
                {
                    if (isTcp)
                    {
                        Sessions.CloseSession(session.ID); return;
                    }
                    else
                    {
                        DisconnectUDP(UdpSession); return;
                    }
                }

                if (image == null)//如果服务器内存中无此文件
                {
                    image = new ServerFileInfo(FileMsg.MD5);//创建内存文件
                    Images.Add(image.MD5, image);//将内存文件添加到文件下载服务区
                    if (File.Exists(fullFileName))//如果文件已在服务器硬盘中
                    {
                        image.Data = OpeFile.Read(fullFileName);//将文件数据全部读取到内存
                        image.Length = image.Data.Length;//设置准确的文件长度
                        image.currLength = image.Length;//标记文件已经全部上传完成
                        FileMsg.CommandType = (byte)FileServerCommandType.Over;//标识文件已经传输完成，通知客户端停止上传
                        if (isTcp)
                        {
                            Sessions.SendTo(FileMsg.Header, session.ID);
                            Sessions.CloseSession(session.ID); return;
                        }
                        else
                        {
                            udpServer.Send(FileMsg.Header, UdpSession);
                            DisconnectUDP(UdpSession); return;
                        }
                    }
                    else
                    {
                        image.Extension = FileMsg.Extension;
                        image.Length = FileMsg.Length;
                        image.Data = new byte[image.Length];//创建内存空间

                        FileMsg.CommandType = (byte)FileServerCommandType.Set;//通知客户端上传
                        FileMsg.LastLength = 0;//上传位置从零开始
                        if (isTcp)
                            Sessions.SendToAsync(FileMsg.Header, session.ID, null);
                        else
                            udpServer.Send(FileMsg.Header, UdpSession);
                    }
                }
                else//如果服务器内存中有此文件
                {
                    FileMsg.CommandType = (byte)FileServerCommandType.Over;//标识文件已经传输完成，通知客户端停止上传
                    if (isTcp)
                    {
                        Sessions.SendTo(FileMsg.Header, session.ID);
                        Sessions.CloseSession(session.ID); return;
                    }
                    else
                    {
                        udpServer.Send(FileMsg.Header, UdpSession);
                        DisconnectUDP(UdpSession); return;
                    }
                }
            }
            #endregion

            #region 下载文件
            if (FileMsg.CommandType == (byte)FileServerCommandType.Get)//下载文件请求
            {
                if (image == null)  //如果内存中无文件
                    if (File.Exists(fullFileName))//如果文件已在服务器硬盘中
                    {
                        image = new ServerFileInfo(FileMsg.MD5);//创建内存文件 
                        image.MD5 = FileMsg.MD5;
                        image.Data = OpeFile.Read(fullFileName);//将文件数据全部读取到内存
                        image.Length = image.Data.Length;//设置准确的文件长度
                        image.currLength = image.Length;//标记文件已经全部上传完成
                        Images.Add(image.MD5, image);//将内存文件添加到文件下载服务区
                    }

                if (image != null && FileMsg.LastLength < image.Data.Length) //如果内存中有文件
                {
                    if (FileMsg.LastLength + 10240 > image.Data.Length)
                        FileMsg.Payload = new byte[image.Data.Length - FileMsg.LastLength];//要发送的缓冲区
                    else
                        FileMsg.Payload = new byte[10240];//要发送的缓冲区

                    Buffer.BlockCopy(image.Data, (int)FileMsg.LastLength, FileMsg.Payload, 0, FileMsg.PayloadLength);//将其保存于Buffer字节数组
                    FileMsg.CommandType = (byte)FileServerCommandType.Get;//下载标记
                    FileMsg.LastLength += FileMsg.PayloadLength;

                    if (isTcp)
                        Sessions.SendTo(FileMsg.ToArray(), session.ID);
                    else
                        udpServer.Send(FileMsg.ToArray(), UdpSession);

                    if (FileMsg.LastLength == image.Data.Length)
                    {
                        FileMsg.CommandType = (byte)FileServerCommandType.Over;//标记下载完成
                        if (isTcp)
                        {
                            Sessions.SendTo(FileMsg.Header, session.ID); 
                            Sessions.CloseSession(session.ID);
                            return;
                        }
                        else
                        {
                            udpServer.Send(FileMsg.Header, UdpSession);
                            DisconnectUDP(UdpSession); return;
                        }
                    }
                }
            }
            #endregion

            #region 上传文件
            if (FileMsg.CommandType == (byte)FileServerCommandType.Set)//上传文件内容
            {
                if (image.currLength + FileMsg.PayloadLength > image.Length) return;
                Buffer.BlockCopy(FileMsg.Payload, 0, image.Data, (int)image.currLength, FileMsg.PayloadLength);//将收到的数据保存于Buffer字节数组
                image.currLength += FileMsg.PayloadLength;//设置最后一次上传文件的末尾长度 
                FileMsg.LastLength = image.currLength;//告诉客户端已上传的文件位置
                FileMsg.Payload = null;

                if (image.currLength == image.Length)//如果文件上传完成
                {
                    OpeFile.Write(image.Data, fullFileName);
                    FileMsg.CommandType = (byte)FileServerCommandType.Over;//标识文件已经传输完成，通知客户端停止上传
                    if (isTcp)
                    {
                        Sessions.SendTo(FileMsg.Header, session.ID);
                        Sessions.CloseSession(session.ID); return;
                    }
                    else
                    {
                        udpServer.Send(FileMsg.Header, UdpSession);
                        DisconnectUDP(UdpSession); return;
                    } 
                }
                else//通知客户端上传文件下一数据包
                {
                    if (isTcp)
                        Sessions.SendTo(FileMsg.Header, session.ID);
                    else
                        udpServer.Send(FileMsg.Header, UdpSession);
                }
            }
            #endregion
        }

        #region 断开UDP连接 
        /// <summary>
        /// 断开UDP连接
        /// </summary>
        /// <param name="session"></param>
        private void DisconnectUDP(NetConnection session)
        {
            session.Disconnect("");
        }
        #endregion

        #endregion
    }
}
